package cfdt.redis.lock;

import cfdt.common.enums.CommonCodeEnum;
import cfdt.common.exception.CodeException;
import cfdt.tools.util.GUID;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DisributedLockAspect {
    
    @Autowired
    private RedisDistributedLock redisDistributedLock;

    @Around(value = "@annotation(cfdt.redis.lock.DistributedLock)")
    public Object doLock(ProceedingJoinPoint joinPoint) throws Throwable {
        
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        DistributedLock lockAnnotation = method.getAnnotation(DistributedLock.class);
        
        //创建ExpressionParser解析表达式
        ExpressionParser parser = new SpelExpressionParser();
        //表达式放置
        Expression exp = parser.parseExpression(lockAnnotation.lockKey());
        
        //创建一个虚拟的容器EvaluationContext
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        Object[] args = joinPoint.getArgs();
        
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        
        for(int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        
        //getValue有参数ctx，从新的容器中根据SpEL表达式获取所需的值
        String lockType = lockAnnotation.lockType();
        String lockKey = lockAnnotation.lockKey();
        if(parameterNames.length > 0) {
            lockKey = exp.getValue(context).toString();
        }
        String redisValue = GUID.getTxNo20();
        int expireTime = lockAnnotation.expireTime();
         
        // 尝试加锁
        boolean tryLock  = redisDistributedLock.tryLock(lockType, lockKey, redisValue, expireTime);
        if(!tryLock) {
            throw new CodeException(CommonCodeEnum.CODE_2003);
        }
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            // 解锁
            redisDistributedLock.releaseLock(lockType, lockKey, redisValue);
        }
    }
    
}
